package org.e_gold.factory;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.log4j.Logger;
import org.e_gold.factory.export.MonitorThread;
import org.e_gold.model.Contract;
import org.e_gold.model.SettledContract;
import org.e_gold.model.UserAccount;
import org.e_gold.service.BrowserService;
import org.e_gold.service.ConfigService;
import org.e_gold.service.SettledContractService;
import org.e_gold.service.UserAccountService;
import org.e_gold.service.impl.ConfigServiceImpl.betConfig;
import org.e_gold.util.CalcUtil;
import org.e_gold.util.LogUtil;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Manager extends Thread {

	private static final Logger logger = Logger.getLogger(Manager.class);

	public static Set<Contract> workingContract = Collections.synchronizedSet(new HashSet<Contract>());

	private static final String TSA_AMOUNT = "amount";
	private static final String TSA_SUCCESSED = "SUCCESSED";
	private static final int TSA_SLEEP_AFTER_SUCCEDD = 15000;

	private float balance = 0f;
	private boolean initialized = false;
	private String SERVICE_TRANSFER_URL;

	@Autowired
	public MonitorThread monitor;

	@Autowired
	private ConfigService configService;	

	@Autowired
	private UserAccountService userAccountService;

	@Autowired
	private SettledContractService settledContractService;

	@Autowired
	private AppProperties appProperties;

	@Autowired
	private BrowserService browserService;

	public void initializeAndStart(){
		configService.set_bet_times(0);

		//reset amount_per_time
		configService.set_bet_amount_per_time(0f);

		//navigate to update online settled contract status
		settledContractService.updateOnlineStatus();

		//navigate to update running settled contract
		update_running_contract();

		//update total running count
		configService.set_bet_current_count(workingContract.size());

		//update bet_amount_per_time
		userAccountService.updateAmount_Per_Time();

		//send first balance to server
		balance = userAccountService.getBalance();
		monitor.offer(balance);

		//start digging
		initialized = true;

		//TSA service url
		SERVICE_TRANSFER_URL = appProperties.getTSAUrl();
	}

	@Override
	public void run() {
		try {
			LogUtil.logStart();
			while (true) {
				if(!initialized){
					initializeAndStart();
				}
				try {
					if(is_able_to_bet()){ //balance != 0f // expose events
						logger.info("Start expose events");
						logger.info("Check balance : " + balance);

						browserService.exposeEvents();
						waitingSeconds(60, 240);
					}

					if (!workingContract.isEmpty()) {
						browserService.waitingResult();
						waitingSeconds(60, 180);
						settledContractService.updateOnlineStatus();
					}
					update_running_contract();
					userAccountService.updateAmount_Per_Time();
					flushBalance();
					waitingSeconds(60, 180);
				} catch (Exception e) {
					logger.info("Maybe internet connection error");
					waitingSeconds(39, 790);
					logger.error(e);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * if current_bet_count == 0 => split to get amount_per_time<br/>
	 * else amount_per_times = full balance
	 * @param balance
	 */

	public static void bet_placed(SettledContract sc){
		workingContract.add(sc.getContract());
	}

	/**
	 * This function is used to check user account status, balance and current betting size limit.<br/>
	 * User must be qualify these contract before betting
	 * <ol>
	 * 		<li>Bet slot is available.</li>		
	 * 		<li>User is <b>active</b></li>
	 * 		<li>Balance is greater than 0</li>
	 * </ol>
	 * @return
	 * @author Thang Nguyen
	 */
	private boolean is_able_to_bet(){
		boolean finalResult = false;
		try{
			UserAccount user = userAccountService.getUser();

			int bet_current_count = configService.get_bet_current_count();
			int bet_times = configService.get_bet_times();
			flushBalance();
			float baseAmount = Float.parseFloat(configService.get(betConfig.BASE_AMOUNT.toString()).getValue());
			int wonPercent = Integer.parseInt(configService.get(betConfig.WON_PERCENT.toString()).getValue());
			float target = baseAmount + (baseAmount * wonPercent / 100);

			float amountToTransfer = 0;

			if(bet_current_count == 0){
				amountToTransfer = balance - target;
				if(amountToTransfer > 0){
					//transfer remain from baseAmount
					beginTransfer(balance - baseAmount);
				}
				//after transfer done, continue bet
				finalResult = user.isActive();
			}else{
				List<SettledContract> lstSettledContract = settledContractService.getRunningSettledContract();
				Float wonAmount = 0f;
				for(SettledContract sc : lstSettledContract){
					wonAmount += sc.getWonAmount();
				}
				amountToTransfer = (balance + wonAmount) - target;
				if(amountToTransfer >= 0f){
					//keep waiting, not bet
					finalResult = false;
				}else{
					if(balance > 0){
						finalResult = user.isActive();
					}else{
						finalResult = false;
					}
				}
			}

			//qualify for next bet. But.. check total slot
			if(finalResult && balance > 0){
				return bet_current_count < bet_times;
			}
		}catch(Exception e){
			logger.error((ToStringBuilder.reflectionToString(e, ToStringStyle.SHORT_PREFIX_STYLE)));
		}
		return false;
	}

	private void beginTransfer(Float amountToTransfer) throws Exception {
		try{
			if(amountToTransfer < 0.1f){
				throw new Exception("Amount is not enough to transfer");
			}
			amountToTransfer = CalcUtil.rountingBalance(amountToTransfer);
			Map<String, String> dataTransfer = new HashMap<String, String>();
			dataTransfer.put(TSA_AMOUNT, amountToTransfer.toString());

			Connection conn = Jsoup.connect(SERVICE_TRANSFER_URL);
			conn.data(dataTransfer);
			Document doc = conn.timeout(30000).get();

			String strResult = doc.body().text();
			if(StringUtils.equals(strResult, TSA_SUCCESSED)){
				try{
					Thread.sleep(TSA_SLEEP_AFTER_SUCCEDD);
				} finally{
					userAccountService.updateAmount_Per_Time();
					flushBalance();
					logger.info("TSA completed. Transfer [" + amountToTransfer + "] :: " + balance);
				}
			}
		}catch(Exception ex){
			throw ex;
		}
	}

	private void flushBalance(){
		balance = userAccountService.getBalance();
		monitor.offer(balance);
	}

	/**
	 * Get current bet count and comparing to bet slot 
	 * @return
	 * @author Thang Nguyen
	 */
	@SuppressWarnings("unused")
	@Deprecated
	private boolean is_bet_slot_full(){
		return userAccountService.is_bet_slot_full();
	}

	public static void match_done(SettledContract running){
		for(Iterator<Contract> i = workingContract.iterator(); i.hasNext();){
			Contract c = i.next();
			if(c.getId().longValue() == running.getContract().getId().longValue()){
				i.remove();
			}
		}
		workingContract.remove(running.getContract());
	}


	private void update_running_contract(){
		List<SettledContract> lstSC = settledContractService.getRunningSettledContract();
		workingContract.clear();
		if (CollectionUtils.isNotEmpty(lstSC)) {
			// is anything running

			for(SettledContract sc : lstSC){
				if (sc != null) {
					logger.info("Waiting result of: " + sc.getId() + ":" + sc.getBetSlip());

					monitor.offer(sc.clone());
					workingContract.add(sc.getContract());
				}
			}
		} 
	}

	// waiting random minute from - to , ex : 5-10 minutes
	// production
	@SuppressWarnings("unused")
	private void waitingMinutes(int from, int to) throws InterruptedException {

		Float waitingRandomSeconds = (float) CalcUtil.randomTime(from * 60, to * 60);
		Float waitingNextTime = waitingRandomSeconds / 60;
		LogUtil.logWaitingNextTime(waitingNextTime, " minutes");
		Thread.sleep((int) (waitingRandomSeconds * 1000));
	}

	// development
	private void waitingSeconds(int from, int to) throws InterruptedException {

		Float waitingRandomSeconds = (float) CalcUtil.randomTime(from, to);
		Float waitingNextTime = waitingRandomSeconds;
		LogUtil.logWaitingNextTime(waitingNextTime, "seconds");
		Thread.sleep((int) (waitingRandomSeconds * 1000));
	}

	public void setBrowserService(BrowserService browserService){
		this.browserService = browserService;
	}

}
